/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or https://opensource.org/licenses/CDDL-1.0.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2013, 2018 by Delphix. All rights reserved.
 * Copyright (c) 2016, 2017 Intel Corporation.
 * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>.
 * Copyright (c) 2023 DilOS
 */

#include <assert.h>
#include <devid.h>
#include <errno.h>
#include <fcntl.h>
#include <libdiskmgt.h>
#include <libintl.h>
#include <libnvpair.h>
#include <libzutil.h>
#include <limits.h>
#include <sys/spa.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/efi_partition.h>
#include <sys/stat.h>
#include <sys/vtoc.h>
#include <sys/mntent.h>
#include <ctype.h>

#include "zpool_util.h"
#include <sys/zfs_context.h>

void
libdiskmgt_error(int error)
{
	/*
	 * ENXIO/ENODEV is a valid error message if the device doesn't live in
	 * /dev/dsk.  Don't bother printing an error message in this case.
	 */
	if (error == ENXIO || error == ENODEV)
		return;

	(void) fprintf(stderr, gettext("warning: device in use checking "
	    "failed: %s\n"), strerror(error));
}

/*
 * Validate a device, passing the bulk of the work off to libdiskmgt.
 */
static int
check_slice(const char *path, int force, boolean_t wholedisk, boolean_t isspare)
{
	char *msg;
	int error = 0;
	dm_who_type_t who;

	if (force)
		who = DM_WHO_ZPOOL_FORCE;
	else if (isspare)
		who = DM_WHO_ZPOOL_SPARE;
	else
		who = DM_WHO_ZPOOL;

	if (dm_inuse((char *)path, &msg, who, &error) || error) {
		if (error != 0) {
			libdiskmgt_error(error);
			return (0);
		} else {
			vdev_error("%s", msg);
			free(msg);
			return (-1);
		}
	}

	/*
	 * If we're given a whole disk, ignore overlapping slices since we're
	 * about to label it anyway.
	 */
	error = 0;
	if (!wholedisk && !force &&
	    (dm_isoverlapping((char *)path, &msg, &error) || error)) {
		if (error == 0) {
			/* dm_isoverlapping returned -1 */
			vdev_error(gettext("%s overlaps with %s\n"), path, msg);
			free(msg);
			return (-1);
		} else if (error != ENODEV) {
			/* libdiskmgt's devcache only handles physical drives */
			libdiskmgt_error(error);
			return (0);
		}
	}

	return (0);
}

/*
 * Validate a whole disk.  Iterate over all slices on the disk and make sure
 * that none is in use by calling check_slice().
 */
static int
check_disk(const char *name, dm_descriptor_t disk, int force, int isspare)
{
	dm_descriptor_t *drive, *media, *slice;
	int err = 0;
	int i;
	int ret;

	/*
	 * Get the drive associated with this disk.  This should never fail,
	 * because we already have an alias handle open for the device.
	 */
	if ((drive = dm_get_associated_descriptors(disk, DM_DRIVE,
	    &err)) == NULL || *drive == 0) {
		if (err)
			libdiskmgt_error(err);
		return (0);
	}

	if ((media = dm_get_associated_descriptors(*drive, DM_MEDIA,
	    &err)) == NULL) {
		dm_free_descriptors(drive);
		if (err)
			libdiskmgt_error(err);
		return (0);
	}

	dm_free_descriptors(drive);

	/*
	 * It is possible that the user has specified a removable media drive,
	 * and the media is not present.
	 */
	if (*media == 0) {
		dm_free_descriptors(media);
		vdev_error(gettext("'%s' has no media in drive\n"), name);
		return (-1);
	}

	if ((slice = dm_get_associated_descriptors(*media, DM_SLICE,
	    &err)) == NULL) {
		dm_free_descriptors(media);
		if (err)
			libdiskmgt_error(err);
		return (0);
	}

	dm_free_descriptors(media);

	ret = 0;

	/*
	 * Iterate over all slices and report any errors.  We don't care about
	 * overlapping slices because we are using the whole disk.
	 */
	for (i = 0; slice[i] != 0; i++) {
		char *name = dm_get_name(slice[i], &err);

		if (check_slice(name, force, B_TRUE, isspare) != 0)
			ret = -1;

		dm_free_name(name);
	}

	dm_free_descriptors(slice);
	return (ret);
}

/*
 * Validate a device.
 */
int
check_device(const char *path, boolean_t force, boolean_t isspare,
    boolean_t iswholedisk)
{
	(void) iswholedisk;
	dm_descriptor_t desc;
	int err;
	char *dev;

	/*
	 * For whole disks, libdiskmgt does not include the leading dev path.
	 */
	dev = strrchr(path, '/');
	assert(dev != NULL);
	dev++;
	if ((desc = dm_get_descriptor_by_name(DM_ALIAS, dev, &err)) != 0) {
		err = check_disk(path, desc, force, isspare);
		dm_free_descriptor(desc);
		return (err);
	}

	return (check_slice(path, force, B_FALSE, isspare));
}

int
check_file(const char *file, boolean_t force, boolean_t isspare)
{
	return (check_file_generic(file, force, isspare));
}
